#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <string.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <time.h>
struct Coord{
	char x;
	char y;
	struct Coord *next;
};

char *p;					// 两块地图首地址
unsigned long width;		// 单块地图的宽
unsigned long height;		// 单块地图的高
unsigned long bpp;			// 母鸡
unsigned long screen_size;	// 单块地图的大小

struct Coord *head;			// 蛇头
char snake_dir = 3;			// 0北 1西 2南 3东
char map[12][20] = {0};		// 地图 0空 1豆 2蛇
short grade = 2;				// 分数
struct Coord bean;			// 豆子坐标
char bean_dir;				// 吃豆子所贴的墙
bool haveBean = false;		// 场上是否存在豆子

bool goEat = false;			// 是否直走吃豆
bool goWall = false;
char wall_dir = 0;

char dir[10][18] = {
	{3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 2},
	{0, 2, 1, 1, 1, 1, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 2},
	{0, 2, 3, 3, 3, 3, 2, 0, 3, 3, 3, 3, 3, 3, 3, 2, 0, 2},
	{0, 2, 0, 2, 1, 1, 2, 0, 1, 1, 1, 1, 1, 1, 1, 2, 0, 2},
	{0, 2, 0, 3, 2, 0, 3, 3, 3, 3, 3, 3, 3, 2, 0, 2, 0, 2},
	{0, 2, 0, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 2},
	{0, 2, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 0, 2},
	{0, 2, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 2},
	{0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2},
	{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
};


int colors[] = {  
	0x00000000, // 黑色  
	0x00F88000, // 橙色  
	0x00FFFF00, // 黄色  
	0x0000FF00, // 绿色  
	0x000000FF, // 蓝色  
	0x0000FFAF, // 青色  
	0x00FF00FF  // 紫色  
};

void updateCell(int n, int x, int y, int index);

bool isKeep(){
	if(head->x < 1 || head->x > 18)
		return false;
	if(head->y < 1 || head->y > 10)
		return false;
	return true;
}

void getBeanDir(){
	char north = bean.y;
	char west = bean.x;
	char south = 11 - north;
	char east = 19 - west;
	char dir;
	if(north < 6){
		dir = (east == 2) ? 3 : 0;
	} else{
		dir = (west == 2) ? 1 : 2;
	}
	bean_dir = dir;
}

void randBean(int n){
	if(haveBean)
		return;
	char x, y;
	do{
		x = (rand() + n) % 14 + 2;
		y = (rand() + n) % 8 + 2;
	}while(map[y][x] != 0);
	map[y][x] = 1;
	bean.x = x;
	bean.y = y;
	bean.next = NULL;
	updateCell(n, x, y, 4);
	updateCell(n+1, x, y, 4);
	haveBean = true;
	getBeanDir();
}

void getNext(char *x, char *y, char dir){
	switch(dir){
	case 0:
		(*y)--;
		break;
	case 1:
		(*x)--;
		break;
	case 2:
		(*y)++;
		break;
	case 3:
		(*x)++;
		break;
		default:
			break;
	}
}

bool goEatBean(){
	switch (bean_dir) {
	case 0:
		if(bean.x == head->x && bean.y > head->y){
			goEat = true;
		}
		break;
	case 1:
		if(bean.y == head->y && bean.x > head->x){
			goEat = true;
		}
		break;
	case 2:
		if(bean.x == head->x && bean.y < head->y){
			goEat = true;
		}
		break;
	case 3:
		if(bean.y == head->y && bean.x < head->x){
			goEat = true;
		}
		break;
		default:
			//TODO
			break;
	}
	if(goEat){
		return true;
	}
	return false;
}

bool inMap(char x, char y){
	if(x < 1 || x > 18)
		return false;
	if(y < 1 || y > 10)
		return false;
	return true;
}

void sideWallk(){
	char dir[3] = {(snake_dir + 1) % 4, snake_dir, (snake_dir + 3) % 4};
	for(int i = 0; i < 3; i++){
		char turn_x = head->x, turn_y = head->y;
		getNext(&turn_x, &turn_y, dir[i]);
		if(inMap(turn_x,turn_y) && map[turn_y][turn_x] == 0 || map[turn_y][turn_x] == 1){
			snake_dir = dir[i];
			return;
		}
	}
}

void getNearWall(){
	char north = head->y;
	char west = head->x;
	char south = 11 - north;
	char east = 19 - west;
	char dir;
	if(north < 6){
		dir = (east == 2) ? 3 : 0;
	} else{
		dir = (west == 2) ? 1 : 2;
	}
	wall_dir = dir;
}

void getSnakeDir(){
	char front_x = head->x, front_y = head->y;
	getNext(&front_x, &front_y, snake_dir);
	if(!inMap(front_x, front_y)){
		snake_dir = (snake_dir + 3) % 4;
		goWall = false;
		return;
	}
	
	if(goWall){
		if(snake_dir != wall_dir){
			char left_dir = (snake_dir + 1) % 4;
			char left_x = head->x, left_y = head->y;
			getNext(&left_x, &left_y, left_dir);
			if(map[left_y][left_x] != 2){
				snake_dir = left_dir;
			}
		}
	} else if(goEat){
		return;
	} else if(goEatBean()){
		snake_dir = (bean_dir + 2) % 4;
	} else{
		sideWallk();
	}
}

void trace(){
	char now_x = head->x, now_y = head->y;
	snake_dir = dir[now_y - 1][now_x - 1];
}

bool moveSnake(int n){
	char next_x = head->x, next_y = head->y;
	getNext(&next_x, &next_y, snake_dir);
	struct Coord *s = (struct Coord*)malloc(sizeof(struct Coord));
	s->x = next_x;
	s->y = next_y;
	s->next = head;
	head = s;
	
	if(map[next_y][next_x] == 1){
		map[next_y][next_x] = 2;
		updateCell(n, next_x, next_y, 3);
		updateCell(n+1, next_x, next_y, 3);
		haveBean = false;
		goEat = false;
		goWall = true;
		getNearWall();
		grade++;
		printf("grade: %d\n", grade);
		return true;
	} else if(map[next_y][next_x] == 2){
		return false;
	}
	struct Coord *e = s->next;
	while(e->next != NULL){
		s = e;
		e = e->next;
	}
	updateCell(n, e->x, e->y, 0);
	updateCell(n+1, e->x, e->y, 0);
	s->next = NULL;
	map[e->y][e->x] = 0;
	free(e);
	map[next_y][next_x] = 2;
	updateCell(n, next_x, next_y, 3);
	updateCell(n+1, next_x, next_y, 3);
	return true;
}

void updateCell(int n, int x, int y, int index){
	int coord = y * 40 * width + x * 40 + width + 1;
	char *c = p + screen_size*(n%2) + coord*4;
	for(int i = 1; i < 39; i++){
		for(int j = 1; j < 39; j++, c+=4)
			memcpy(c, &colors[index], 4);
		c += (width - 38)* 4;
	}
}

void printMap(int n, int x, int y){
	int color = 0;
	if(map[y][x] == 2){
		color = 3;
	} else if(map[y][x] == 1){
		color = 4;
	}else if(x == 0 || x == 19 || y == 0 || y == 11){
		color = 1;
	}
	updateCell(n, x, y, color);
}

void initMap(){
	struct Coord *h = (struct Coord*)malloc(sizeof(struct Coord));
	h->x = 2;
	h->y = 1;
	map[1][2] = 2;
	struct Coord *e = (struct Coord*)malloc(sizeof(struct Coord));
	e->x = 1;
	e->y = 1;
	map[1][1] = 2;
	e->next = NULL;
	h->next = e;
	head = h;
	randBean(0);
	for(int n = 0; n < 2; n++){
		for(int y = 0; y < 12; y++){
			for(int x = 0; x < 20; x++){
				printMap(n, x, y);
			}
		}
	}
}

int main()
{
	srand((unsigned int)time(NULL));
	// 打开LCD设备
	int lcd = open("/dev/fb0", O_RDWR|O_EXCL);
	
	struct fb_var_screeninfo vinfo; // 显卡设备的可变属性结构体
	ioctl(lcd, FBIOGET_VSCREENINFO, &vinfo); // 获取可变属性
	
	// 获得当前显卡所支持的虚拟区显存大小
	width  = vinfo.xres;
	height = vinfo.yres;
	bpp    = vinfo.bits_per_pixel;
	screen_size = width * height * bpp/8;
	
	// 申请一块两倍与屏幕的映射内存
	p = mmap(NULL, 2 * screen_size,
		PROT_READ|PROT_WRITE,
		MAP_SHARED, lcd, 0); 
	
	bzero(p, 2*screen_size);
	
	// 将起始可见区设定为B区
	vinfo.xoffset = 0;
	vinfo.yoffset = 480;
	ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);
	initMap();
	sleep(1);
	
	bool isSafe = true;
	int n;
	for(n=0; isKeep() && isSafe; n++)
	{        
		vinfo.xoffset = 0;
		vinfo.yoffset = 480*(n%2);
		ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);
		getSnakeDir();
		isSafe = moveSnake(n);
		randBean(n);
		usleep(20000);
		if(grade >= 50){
			printf("一阶段结束，进入二阶段\n");
			break;
		}
	}
	for(int i = 0; i < 50;i++){
		vinfo.xoffset = 0;
		vinfo.yoffset = 480*(n%2);
		ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);
		sideWallk();
		isSafe = moveSnake(n);
		randBean(n);
		usleep(20000);
	}
	for(; isKeep() && isSafe; n++)
	{        
		vinfo.xoffset = 0;
		vinfo.yoffset = 480*(n%2);
		ioctl(lcd, FBIOPAN_DISPLAY, &vinfo);
		trace();
		isSafe = moveSnake(n);
		randBean(n);
		usleep(5000);
		if(grade >= 104){
			printf("!!!Win!!!\n");
			break;
		}
	}
	return 0;
}
